Skip to content

[中文版本](/posts/c教程/zh-CN/07-贴近系统/061-System Calls and the Standard Library)

When you write C programs that touch files, processes, or devices, you’re talking to the operating system, not directly to hardware. That communication happens through system calls.

System calls are the lowest-level interface between user-space programs and the OS kernel. C’s standard library (libc) is a thin layer of wrappers built on top of those calls, making them easier to use and more portable.

Let’s explore how this works, and how to use system calls directly from your C code.

Step 1. What Is a System Call? ​

A system call (syscall) lets a program request a service from the OS kernel, like reading a file, creating a process, or allocating memory.

Examples:

  • read(),write()– access files and devices
  • fork(),exec()– create and manage processes
  • open(),close()– handle file descriptors
  • mmap()– map files into memory
  • socket()– network communication

When you call a system function, control passes from user space to kernel space, then back again.

Step 2. The Role of libc ​

The C standard library (glibc, musl, etc.) provides wrappers around these system calls.

Example:

#include <stdio.h>
int main(void) {
    FILE *f = fopen("test.txt", "r");
    if (!f) {
        perror("fopen failed");
        return 1;
    }
    fclose(f);
}

Under the hood,fopen() eventually callsopen(), a system call defined in<fcntl.h>. You can call it directly too.

Step 3. Calling System Calls Directly ​

Here’s the same operation without stdio helpers:

#include <fcntl.h>     // open
#include <unistd.h>    // read, write, close
#include <stdio.h>     // perror
int main(void) {
    int fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open failed");
        return 1;
    }
    char buf[128];
    ssize_t n = read(fd, buf, sizeof(buf) - 1);
    if (n >= 0) {
        buf[n] = '\0';
        write(STDOUT_FILENO, buf, n);
    }
    close(fd);
}

Compile and run:

gcc sysread.c -o sysread
./sysread

This prints the first 128 bytes oftest.txt directly using system calls, nofopen() orprintf() involved.

Tiny Code: Minimal System Call Example ​

Let’s drop even the C library and use a raw syscall interface.

#include <unistd.h>
int main(void) {
    const char msg[] = "Hello via system call\n";
    write(1, msg, sizeof(msg) - 1);  // 1 = STDOUT
    _exit(0);
}

Compile:

gcc -nostdlib -static syshello.c -o syshello

Run:

Hello via system call

You just executed a system call directly, bypassing the standard library entirely.

Step 4. File Descriptors ​

System calls likeread() andwrite() work with file descriptors, small integer handles managed by the OS.

DescriptorMeaning
0Standard input (stdin)
1Standard output (stdout)
2Standard error (stderr)

Every open file, socket, or pipe has a unique descriptor.

Example:

write(1, "Output to stdout\n", 17);
write(2, "Output to stderr\n", 17);

Step 5. Inspecting System Calls ​

You can watch your program’s system calls usingstrace:

strace ./sysread

Example output:

open("test.txt", O_RDONLY) = 3
read(3, "Hello World\n", 12) = 12
write(1, "Hello World\n", 12) = 12
close(3) = 0

This shows the real kernel-level operations, a great debugging and learning tool.

Step 6. Return Values and Errors ​

System calls usually return:

  • ≥ 0 → success (bytes read, process ID, etc.)
  • -1 → error (witherrno set to an error code)

Example:

#include <errno.h>
#include <string.h>
int fd = open("missing.txt", O_RDONLY);
if (fd == -1)
    fprintf(stderr, "Error: %s\n", strerror(errno));

Common error codes:

CodeMeaning
ENOENTFile not found
EACCESPermission denied
EBADFInvalid descriptor
EINTRInterrupted system call

Step 7. Mixing System Calls and stdio ​

You can combine both layers safely, just don’t mix them on the same file descriptor.

Example (safe):

FILE *f = fopen("log.txt", "w");
write(STDOUT_FILENO, "Console log\n", 12);
fprintf(f, "File log\n");
fclose(f);

Example (unsafe):

FILE *f = fopen("data.txt", "w");
write(fileno(f), "mixed output\n", 13);  // may confuse buffering

Step 8. System Call Wrappers ​

The Linux kernel provides hundreds of system calls. You can call most through<unistd.h>, but for rare ones, you can usesyscall():

#include <sys/syscall.h>
#include <unistd.h>
int main(void) {
    syscall(SYS_write, 1, "Hello syscall\n", 14);
    return 0;
}

Step 9. Viewing Available Syscalls ​

Check all available system calls for your platform:

man 2 intro

or

ausyscall --dump | head

You’ll see a list like:

read, write, open, close, stat, fork, execve, mmap, ...

Step 10. Why It Matters ​

System calls are the foundation of everything in C and Unix-like systems.

They give your program direct access to:

  • Files and devices
  • Processes and signals
  • Memory and networking
  • Time and environment

Learning to use them directly is essential for understanding how higher-level abstractions (likestdio,pthreads, or sockets) are built.

Try It Yourself ​

  1. Usestrace onls orcat to see how system calls drive them.
  2. Replace afopen()/fread() pair with directopen() andread() calls.
  3. Write a tiny file copier using onlyopen,read,write, andclose.
  4. Experiment with invalid file descriptors and print outerrno.
  5. Build a version ofecho that uses only raw system calls.

Next, you’ll take this a step further: learning how to create and manage processes withfork() andexec(), the heart of Unix multitasking.

本站总访问 — 次